Android Devs, make your Toolbar and View backgrounds frosty

Ali Muzaffar
AndroidPub
9 min readOct 15, 2016

--

The blurry, frosted background is a common pattern on iOS where they have simply controls to achieve this. On Android however, this ends up being a lot of effort. On the up side, if you do bother to handle this yourself, you can achieve better results than you can using the default controls on iOS.

Before I begin, yes, there are libraries out there that will do this for you. If that’s all you’re interested in and don’t want to super power of being able to do this yourself. Just scroll down to “A note on libraries that can do this” section.

We are going to cover 2 basic design patterns when implementing frosted backgrounds.

  1. Frosted header with the content scrolling behind it (pictured above).
  2. A CardView type look where the frosting is in the middle of the screen and can scroll up and down (pictured below).

The “Frosty” or “Frosted glass” effect is achieved by first blurring the picture and then lightening the image. This much will remain constant, however the 2 designs use slightly different approaches to achieve the end look. In Part 1, we will cover how to implement the frosted Toolbar look. Then, in Part 2, we will cover how show a frosted background that floats in the center of the screen (almost like a CardView).

Part 1, implementations of the Frosty Toolbar

To implement the frost toolbar, there are 3 basic steps:

  1. Setup your layouts.
  2. Capture the background image and make a blurred and lightened copy, then set it in your layout.
  3. When you scroll your ScrollView or NestedScrollView, scroll your blurred copy as well.

For me personally, of the 3 steps, the hardest one was to setup your layouts so that it would be possible to achieve this look. Unfortunately, I decided to start with the Scrolling Activity template which I feel only complicated things for me. At any rate, here are the steps.

Set up your layouts

We will need 2 ImageViews, one to hold your background image, the second to hold a blurred copy. Our layout looks like this:

imgBg is where the original image is going to be held, while imgBgBlur is where I will hold the blurred copy of the image.

Capture the background image and make a blurred and lightened copy, then set it in your layout

In the layout above, we are going to capture the entire LinearLayout inside the NestedScrollView. Then we are going to Blur it using RenderScript and then lighten the Bitmap. Its worth noting that you can you don’t have to use RenderScript to blur the image, you can use any number of fast blurring algorithm on the internet. I used RenderScript because it can take better advantage of multi-core processors and runs a lot faster than Java code. You can read more here. I also used RenderScript because some of the Java code for blurring was not playing well on older versions of Android and RenderScript should be backward compatible up to Android 2.3.

First, in your apps build.gradle file, add support for RenderScript.

android {
defaultConfig {

renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
}

Now in our Activity we can capture the View we want to blur. Since we are going to draw the View on to a Bitmap using Canvas, we can also apply a lightening filter at the same time. Now that we have captured our View in a Bitmap and lightened it, we can blur it using RenderScript.

ImageView imgBgBlur = (ImageView) findViewById(R.id.imgBgBlur);
Bitmap mBlurBitmap = createBlurBitmap();
imgBgBlur.setImageBitmap(mBlurBitmap);
public Bitmap createBlurBitmap() {
View viewContainer = findViewById(R.id.container);
Bitmap bitmap = captureView(viewContainer);
if (bitmap != null) {
ImageHelper.blurBitmapWithRenderscript(
RenderScript.create(this),
bitmap);
}
return bitmap;
}
public Bitmap captureView(View view) {
//Create a Bitmap with the same dimensions as the View
Bitmap image = Bitmap.createBitmap(view.getMeasuredWidth(),
view.getMeasuredHeight(),
Bitmap.Config.ARGB_4444); //reduce quality
//Draw the view inside the Bitmap
Canvas canvas = new Canvas(image);
view.draw(canvas);

//Make it frosty
Paint paint = new Paint();
paint.setXfermode(
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
ColorFilter filter =
new LightingColorFilter(0xFFFFFFFF, 0x00222222); // lighten
//ColorFilter filter =
// new LightingColorFilter(0xFF7F7F7F, 0x00000000); // darken
paint.setColorFilter(filter);
canvas.drawBitmap(image, 0, 0, paint);
return image;
}

Is a call to a helper function to blur the image… in case you didn’t pick up on it, this is the function call:

ImageHelper.blurBitmapWithRenderscript(
RenderScript.create(this),
bitmap);

I created this helper method to use RenderScript to blur a bitmap because it’s reusable code. The code looks like this:

public static void blurBitmapWithRenderscript(
RenderScript rs, Bitmap bitmap2) {
// this will blur the bitmapOriginal with a radius of 25
// and save it in bitmapOriginal
// use this constructor for best performance, because it uses
// USAGE_SHARED mode which reuses memory
final Allocation input =
Allocation.createFromBitmap(rs, bitmap2);
final Allocation output = Allocation.createTyped(rs,
input.getType());
final ScriptIntrinsicBlur script =
ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// must be >0 and <= 25
script.setRadius(25f);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmap2);
}

When you scroll your ScrollView or NestedScrollView, move your blurred copy as well

Since we are using the CollapsingToolbarLayout, we will need to detect when our Toolbar collapses. When the Toolbar collapses, we want to position our blurred image behind our NestedScrollView and move it at the same time as the NestedScrollView.

final NestedScrollView nestedScrollView =
(NestedScrollView) findViewById(R.id.nestedScrollView);
final AppBarLayout appBarLayout =
(AppBarLayout) findViewById(R.id.app_bar);
appBarLayout.addOnOffsetChangedListener(
new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout,
int verticalOffset) {
if (Math.abs(verticalOffset)
>= appBarLayout.getTotalScrollRange()) {
Log.d("TAG", "Collapsed");
// Collapsed
} else {
//Log.d("TAG", "Top for BlurImg" + imgBgBlur.getTop());
imgBgBlur.setVisibility(View.GONE);
}
}
});

nestedScrollView.setOnScrollChangeListener(
new NestedScrollView.OnScrollChangeListener() {

@Override
public void onScrollChange(NestedScrollView v,
int scrollX, int scrollY,
int oldScrollX, int oldScrollY) {
imgBgBlur.setTop(v.getTop() - scrollY);
imgBgBlur.setVisibility(View.VISIBLE);
}
});

In the code above, once we detect that the Toolbar has collapsed, we make the blurred ImageView visible. Then in our OnScrollChangeListener, we use setTop() to set the top position of the blurred ImageView relative to it’s parent (which should be the same as our NestedScrollView). The result, is shown below.

The video compression seems to have made it a little hard to see the effect properly. However, you can head over to GitHub and build the app from source yourself.

Part 2, frosted background on Views

To implement a frosted background on Views, the steps are pretty much the same as for the toolbar effect.

  1. Setup your layout
  2. Capture your background View and blur and lighten the image.
  3. Figure out where your target View intersects your background View.
  4. Capture the correct area on the blurred image, give it rounded corners and set it as the background on your View.

Setup your layout

Inside a Frame or Relative layout, we are going to position an ImageView which will hold our background image. There will be a ScrollView which covers the background image and will hold all of our Views that can have a frosted background.

Capture your background View

We already covered a this is the previous section. The steps here aren’t any different, cache a blurred and lightened copy of the background View so you don’t have to repeatedly perform this expensive operations.

Figure out where your target View intersects your background View and capture the area

We want to capture the background View from the X, Y position of the target view to the X + width and y + height. We are also going to scale our captured background down to about half it’s size. This is going to increase the blurring effect as well as increase scrolling performance by reducing our memory footprint.

Matrix matrix = new Matrix();
//half the size of the cropped bitmap
//to increase performance, it will also
//increase the blur effect.
matrix.setScale(0.5f, 0.5f);
Bitmap bitmap = Bitmap.createBitmap(blurredBitmap,
(int) targetView.getX(),
(int) targetView.getY(),
targetView.getMeasuredWidth(),
targetView.getMeasureHeight(),
matrix,
true);

As a fair warning, if your Views are going to Scroll or appear below the screen, the code about will get a little more complex. You can see this the full code in GitHub in the CardViewActivity.

Giving your background Bitmap rounded corners

I have created a helper method to do this, which you can see below.

public static Bitmap roundCorners(Bitmap bitmap,
int cornerRadiusInPixels,
boolean captureCircle) {
Bitmap output = Bitmap.createBitmap(
bitmap.getWidth(),
bitmap.getHeight(),
Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(output);

final int color = 0xffffffff;
final Paint paint = new Paint();
final Rect rect = new Rect(0,
0,
bitmap.getWidth(),
bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = cornerRadiusInPixels;

paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
if (captureCircle) {
canvas.drawCircle(rectF.centerX(),
rectF.centerY(),
bitmap.getWidth() / 2,
paint);
} else {
canvas.drawRoundRect(rectF,
roundPx,
roundPx,
paint);
}

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);

return output;
}

Soon afterwards I realized that AppCompat v4 has a RoundedBitmapDrawable that could replace all the code below with one simple step. One thing I would like to do to improve performance here is to use the RoundedBitmapDrawable instead.

private void setBackgroundOnView(View view, Bitmap bitmap) {
Drawable d;
if (bitmap != null) {
d = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
((RoundedBitmapDrawable) d).setCornerRadius(10);

} else {
d = ContextCompat.getDrawable(CardViewActivity.this, R.drawable.white_background);
}
view.setBackground(d);
}

I’m not sure if this will improve performance; however, it does simplify code and makes the corner radius more predictable. Previously when we create a bitmap with circular corners ourselves, we had to account for the fact that the bitmap was half the original size and would be scaled once it was set as the background image.

Updating the background as you scroll

No real trick here, you update your background onScroll and that’s all there is to it.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
if (mBitmap1 != null) {
mBitmap1.recycle();
}
if (mBitmap2 != null) {
mBitmap2.recycle();
}
mBitmap1 = loadBitmap(imgBg, cardview);
setBackgroundOnView(cardview, mBitmap1);
mBitmap2 = loadBitmap(imgBg, cardview2);
setBackgroundOnView(cardview2, mBitmap2);
}
});

The results are as shown below:

A performance note

While most of the code I have shared above should be enough to get your started. It is worth noting that in order to keep yourself from thrashing your RAM, you should use a Bitmap pool at least when capturing the background View to blur and lighten. For the scrolling Views with the frosty backgrounds, I couldn’t find a way to use a Bitmap pool when creating a new Bitmap by cropping a section of a larger image. Being able to do this would save us a lot of GC overhead (not saying that its not possible) so, I’ll update this post (or create a new one) when I figure out how to do this.

Also worth noting that, I use RenderScript’s RenderIntrinsicBlur to blur images. My understanding is that it this is the fastest algorithm that RenderScript has to to offer for blurring, however there are others algorithms that may be more suited depending on your needs. The Dali library has code for multiple blur algorithm implementations using RenderScript. There is also a good post by its author on StackOverflow explaining the performance of each blurring algorithm.

Keep in mind that it’s probably a good idea to keep the areas your are blurring and your blurred bitmap as small as possible. Holding a large (blurred) bitmaps in memory like I have done above, is probably a bad idea. An other lesson I learned was to make sure that your background image is as small as possible (best quality for the best fit), because having a large image in the background will affect your scrolling performance no matter now efficient you make the code. I noticed that when my images were 1080p and below 1MB in size, scrolling was smooth, when I slapped in 4 or 5MP images, even though they were being cropped by the ImageView, scrolling was terrible! If you are stuck with a huge image, you can use BitmapRegionDecode to get only a portion of the image you want to use as your background image or look at “Load Scaled Down Version Into Memory” section of the Android Developers guide to load scaled down versions of Bitmaps.

Bitmap pools can be tricky to implement, however, if you need to blur images, or the background of Views, you can use libraries for Android that are dedicated to blurring Views and images. This way, you can outsource the headache to someone who (hopefully) knows what they are doing.

A note on libraries that can do this

There are libraries out there that can do the blurred / frosty effect for you. Two such libraries are Dali by Patrick Favre-Bulle and Blurry by wasabeef. These libraries can blur Views for you, load them into another View, keep them updated if the background changes and even apply a color filter on the blurred images. The plus side here is also that you don’t have to manage memory yourself. The downside is, that they can’t do everything (obviously) and, there is something really cool about being able to do this yourself! 😎

Finally

You can find the code on my GitHub page in a repo called FrostyBackgroundTestApp.

In order to build great Android apps, read more of my articles

Yay! you made it to the end! We should hang out! feel free to follow me on Medium, LinkedIn, Google+ or Twitter.

--

--

Ali Muzaffar
AndroidPub

A software engineer, an Android, and a ray of hope for your darkest code. Residing in Sydney.